package com.sola_aventuro.adventure.xml;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;

import com.sola_aventuro.adventure.model.Adventure;
import com.sola_aventuro.adventure.model.Attribute;
import com.sola_aventuro.adventure.model.AttributeType;
import com.sola_aventuro.adventure.model.Character;
import com.sola_aventuro.adventure.model.ConditionType;
import com.sola_aventuro.adventure.model.Item;
import com.sola_aventuro.adventure.model.Link;
import com.sola_aventuro.adventure.model.ObjectFactory;
import com.sola_aventuro.adventure.model.Scene;

/**
 * This class is a handler for XML files.
 * @author xisberto
 *
 */
public class XMLHandler extends DefaultHandler {
	private final static String NAMESPACE = "http://sola-aventuro.com/adventure/model";
	private enum Context {advent, charac, scene};
	private Context context;
	private ObjectFactory fac;
	private Adventure adventure;
	private Scene currentScene;
	private Link currentLink;
	private Character currentChar;
	private Attribute currentAttr;
	private Item currentItem;
	private String buffer;
	private XmlSerializer serializer;

	public XMLHandler() {
		super();
		fac = new ObjectFactory();
		buffer = "";
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		//Appends buffer and trims it
		buffer = buffer + new String(ch, start, length);
		buffer = buffer.trim();
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if (qName.equals("adv:text")) {
			//text elements are present only inside scene elements
			currentScene.setText(buffer);
		} else if (qName.equals("adv:link")) {
			//link elements are present only inside scene elements
			//they have a string content that should be set to their value
			currentLink.setValue(buffer);
			currentScene.getLink().add(currentLink);
		} else if (qName.equals("adv:attribute")) {
			//attribute elements are present only inside character elements
			//they have a string content that should be set to their value
			currentAttr.setValue(buffer);
			currentChar.getAttribute().add(currentAttr);
		} else if (qName.equals("adv:scene")) {
			//scene elements are present only inside adventure elements
			adventure.getScene().add(currentScene);
			context = Context.advent;
		} else if (qName.equals("adv:character")) {
			//character elements are present only inside adventure elements
			adventure.getCharacter().add(currentChar);
			context = Context.advent;
		} else if (qName.equals("adv:enemy")) {
			//enemy elements are present only inside scene elements
			currentScene.getEnemy().add(currentChar);
			context = Context.scene;
		} else if (qName.equals("adv:item")) {
			//item elements can be present inside character or scene elements
			switch (context) {
			case scene:
				currentScene.getItem().add(currentItem);
				break;
			case charac:
				currentChar.getItem().add(currentItem);
				break;
			}
		}
		//clearing the buffer, as we have used it
		buffer = "";
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		if (qName.equals("adv:adventure")) {
			adventure = fac.createAdventure();
			adventure.setName(attributes.getValue("adv:name"));
			adventure.setAuthor(attributes.getValue("adv:author"));
			adventure.setLicense(attributes.getValue("adv:license"));
			adventure.setDificulty(Integer.parseInt(attributes.getValue("adv:dificulty")));
			adventure.setVersion(attributes.getValue("adv:version"));
			context = Context.advent;
		} else if (qName.equals("adv:scene")) {
			currentScene = fac.createScene();
			currentScene.setNumber(Integer.parseInt(attributes.getValue("adv:number")));
			context = Context.scene;
		} else if (qName.equals("adv:link")) {

			currentLink = fac.createLink();
			currentLink.setCondition(ConditionType.fromValue(attributes.getValue("adv:condition")));
			currentLink.setConditionRequeriment(attributes.getValue("adv:condition_requeriment"));
			currentLink.setTarget(Integer.parseInt(attributes.getValue("adv:target")));
		} else if (qName.equals("adv:"+"character") || (qName.equals("adv:enemy"))) {
			currentChar = fac.createCharacter();
			currentChar.setName(attributes.getValue("adv:name"));
			context = Context.charac;
		} else if (qName.equals("adv:attribute")) {
			currentAttr = fac.createAttribute();
			currentAttr.setName(attributes.getValue("adv:name"));
			currentAttr.setType(AttributeType.fromValue(attributes.getValue("adv:type")));
		} else if (qName.equals("adv:item")) {
			currentItem = fac.createItem();
			currentItem.setName(attributes.getValue("adv:name"));
			currentItem.setModifies(attributes.getValue("adv:modifies"));
			currentItem.setBonus(Integer.parseInt(attributes.getValue("adv:bonus")));
		}
			
	}
	
	/**
	 * Uses SAXParser to parse a XML file and generate a Adventure object from them.
	 * @param xml_file a XMLFile formated as Adventure.xsd determines.
	 * @return A Adventure object according with the parsing of xml_file.
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @see Adventure
	 */
	public Adventure parse(File xml_file) throws ParserConfigurationException, SAXException, IOException {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser parser = factory.newSAXParser();
		parser.parse(xml_file, this);
		return adventure;
	}
	
	private void writeScene(String depth, Scene scene) throws IllegalArgumentException, IllegalStateException, IOException {
		serializer.text(depth);
		serializer.startTag(NAMESPACE, "scene");
		serializer.attribute(NAMESPACE, "number", scene.getNumber().toString());
		//The Text tag of the scene
		serializer.text(depth+"\t");
		serializer.startTag(NAMESPACE, "text");
		serializer.text(scene.getText());
		serializer.text(depth+"\t");
		serializer.endTag(NAMESPACE, "text");
		//Writing Link tags
		for (Link link : scene.getLink()) {
			writeLink(depth+"\t", link);
		}
		//Writing Item tags
		for (Item item : scene.getItem()) {
			writeItem(depth+"\t", item);
		}
		//Writing Enemy tags
		for (Character enemy: scene.getEnemy()) {
			writeCharacter(depth+"\t", enemy, false);
		}
		serializer.text(depth);
		serializer.endTag(NAMESPACE, "scene");
	}
	
	private void writeLink(String depth, Link link) throws IllegalArgumentException, IllegalStateException, IOException {
		serializer.text(depth);
		serializer.startTag(NAMESPACE, "link");
		serializer.attribute(NAMESPACE, "target", link.getTarget().toString());
		serializer.attribute(NAMESPACE, "condition", link.getCondition().value());
		serializer.attribute(NAMESPACE, "condition_requeriment", link.getConditionRequeriment());
		serializer.text(link.getValue());
		serializer.endTag(NAMESPACE, "link");
	}
	
	private void writeAttribute(String depth, Attribute attr) throws IllegalArgumentException, IllegalStateException, IOException {
		serializer.text(depth);
		serializer.startTag(NAMESPACE, "attribute");
		serializer.attribute(NAMESPACE, "name", attr.getName());
		serializer.attribute(NAMESPACE, "type", attr.getType().value());
		serializer.text(attr.getValue());
		serializer.endTag(NAMESPACE, "attribute");
	}
	
	private void writeItem(String depth, Item item) throws IllegalArgumentException, IllegalStateException, IOException {
		serializer.text(depth);
		serializer.startTag(NAMESPACE, "item");
		serializer.attribute(NAMESPACE, "name", item.getName());
		serializer.attribute(NAMESPACE, "modifies", item.getModifies());
		serializer.attribute(NAMESPACE, "bonus", item.getBonus().toString());
		serializer.endTag(NAMESPACE, "item");
	}
	
	private void writeCharacter(String depth, Character charac, boolean is_character) throws IllegalArgumentException, IllegalStateException, IOException {
		serializer.text(depth);
		if (is_character) {
			serializer.startTag(NAMESPACE, "character");
		} else {
			serializer.startTag(NAMESPACE, "enemy");
		}
		serializer.attribute(NAMESPACE, "name", charac.getName());
		for (Attribute attr : charac.getAttribute()) {
			writeAttribute(depth+"\t", attr);
		}
		for (Item item : charac.getItem()) {
			writeItem(depth+"\t", item);
		}
		serializer.text(depth);
		if (is_character) {
			serializer.endTag(NAMESPACE, "character");
		} else {
			serializer.endTag(NAMESPACE, "enemy");
		}
	}
	
	/**
	 * Writes a Adventure object to a XML file.
	 * @param adv the Adventure object which will be serialized.
	 * @param xml_file the file to be created.
	 * @throws XmlPullParserException 
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 * @throws IllegalStateException 
	 * @throws IllegalArgumentException 
	 * @see Adventure
	 */
	public void write(Adventure adv, File xml_file) throws XmlPullParserException, IllegalArgumentException, IllegalStateException, FileNotFoundException, IOException {
		//Preparing serializer
		XmlPullParserFactory factory = XmlPullParserFactory.newInstance(
				System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
		serializer = factory.newSerializer();
		serializer.setOutput(new PrintWriter(xml_file));
		//Writing header
		serializer.startDocument("UTF-8", true);
		serializer.setPrefix("adv", NAMESPACE);
		serializer.text("\n");
		//Writing root tag
		serializer.startTag(NAMESPACE, "adventure");
		serializer.attribute(NAMESPACE, "name", adv.getName());
		serializer.attribute(NAMESPACE, "author", adv.getAuthor());
		serializer.attribute(NAMESPACE, "license", adv.getLicense());
		serializer.attribute(NAMESPACE, "dificulty", adv.getDificulty().toString());
		serializer.attribute(NAMESPACE, "version", adv.getVersion());
		//Writing Scene tags
		for (Scene scene : adv.getScene()) {
			writeScene("\n\t", scene);
		}
		//Writing Character tags
		for (Character charac : adv.getCharacter()) {
			writeCharacter("\n\t", charac, true);
		}
		serializer.text("\n");
		serializer.endTag(NAMESPACE, "adventure");
		serializer.endDocument();
	}
	
	public static void main(String[] args) {
		XMLHandler hdl = new XMLHandler();
		
		Adventure adv = null;
		try {
			adv = hdl.parse(new File("adventure.xml"));
		} catch (ParserConfigurationException e1) {
		} catch (SAXException e1) {
		} catch (IOException e1) {
		} finally {
			if (adv == null) {
				adv = new ObjectFactory().createAdventure();
				adv.setName("Teste");
				adv.setAuthor("Xis");
				adv.setLicense("GPLv3");
				adv.setDificulty(5);
				Character chr = new ObjectFactory().createCharacter();
					chr.setName("Personagem");
					Attribute att = new ObjectFactory().createAttribute();
						att.setName("Strenght");
						att.setType(AttributeType.INT);
						att.setValue("5");
					chr.getAttribute().add(att);
					att = new ObjectFactory().createAttribute();
						att.setName("Magic");
						att.setType(AttributeType.INT);
						att.setValue("1");
					chr.getAttribute().add(att);
					Item itm = new ObjectFactory().createItem();
						itm.setName("Short Sword");
						itm.setModifies("Strenght");
						itm.setBonus(1);
					chr.getItem().add(itm);
				adv.getCharacter().add(chr);
			}
		}
		
		Scene scn = new ObjectFactory().createScene();
		scn.setNumber(adv.getScene().size()+1);
		scn.setText("Scene number "+scn.getNumber());
		for (int i = 1; i < 4; i++) {
			Link link = new ObjectFactory().createLink();
			link.setTarget(i);
			link.setCondition(ConditionType.CHOICE);
			link.setConditionRequeriment("uiop");
			link.setValue("Link número "+i);
			scn.getLink().add(link);
		}
		if (new java.util.Random().nextBoolean()){
			Character enemy = new ObjectFactory().createCharacter();
			enemy.setName("Wolf");
			scn.getEnemy().add(enemy);
		} else {
			Item itm = new ObjectFactory().createItem();
			itm.setName("Sword");
			itm.setModifies("Força");
			itm.setBonus(3);
			scn.getItem().add(itm);
		}
		adv.getScene().add(scn);
		
		System.out.println("Scenes:");
		for (Scene scene : adv.getScene()) {
			System.out.println(scene.getText());
			for (Link link : scene.getLink()) {
				System.out.println("-"+link.getValue()+"//"+link.getConditionRequeriment());
			}
		}
		
		try {
			hdl.write(adv, new File("adventure.xml"));
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
